import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang3.StringUtils;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

/**
 * accessKey secretKey 工具类
 *
 * @author dongyang
 * @since 2019-04-22 14:19
 **/
public class AkSkUtils {

    private List<String> allowedMethods = Arrays.asList("GET", "POST", "PUT", "DELETE", "HEAD");

    public static final String TOKEN_VERSION = "v2";

    private static final int TOKEN_PARTS_LENGTH = 4;

    private static final String UTF8_ENCODING = "UTF-8";

    private String accessKey;
    private String secretKey;

    public AkSkUtils(String ak, String sk) {
        this.accessKey = ak;
        this.secretKey = sk;
    }


//    public String generateToken(String urlPath, String method, String queryParam, String body, int expireTime) {
//            if (StringUtils.isEmpty(accessKey) || StringUtils.isEmpty(secretKey)) {
//               // logger.error("invalid AK or SK");
//                throw new RuntimeException("Invalid AK or SK");
//            }
//
//            if (StringUtils.isEmpty(urlPath)) {
//                //logger.error("urlPath can't be empty!");
//                throw new RuntimeException("Empty url path");
//            }
//
//            if (!allowedMethods.contains(method)) {
//               // logger.error("{} isn't an allowed method", method);
//                throw new RuntimeException("invalid request method");
//            }
//
//            String token = "";
//            try {
//                // |v2-{AK}-{ExpireTime}|{SK}|
//                StringBuffer sbSign = new StringBuffer(String.format("|%s-%s-%d|%s|", TOKEN_VERSION,
//                        accessKey, expireTime, secretKey));
//
//                // {UrlPath}|
//                sbSign.append(UriUtils.decode(urlPath, UTF8_ENC)).append("|");
//
//                // {Method}|
//                sbSign.append(method).append("|");
//
//                // {QueryParam}|
//                if (!StringUtils.isEmpty(queryParam)) {
//                    List<String> qsArray = new ArrayList<String>();
//                    for (String kv: queryParam.split("&")) {
//                        String[] t = kv.split("=");
//                        if (t.length > 1) {
//                            qsArray.add(String.format("%s=%s", UriUtils.decode(t[0], UTF8_ENC), UriUtils.decode(t[1], UTF8_ENC)));
//                        } else {
//                            qsArray.add(String.format("%s=", UriUtils.decode(t[0], UTF8_ENC)));
//                        }
//                    }
//
//                    Collections.sort(qsArray);
//                    boolean first = true;
//                    for (String s: qsArray) {
//                        if (first) {
//                            first = false;
//                        } else {
//                            sbSign.append("&");
//                        }
//                        sbSign.append(s);
//                    }
//                }
//                sbSign.append("|");
//
//                // {body}|
//                if (!StringUtils.isEmpty(body)) {
//                    sbSign.append(body);
//                }
//                sbSign.append("|");
//               // logger.info("signature contents: {}", sbSign.toString());
//
//                MessageDigest md5 = MessageDigest.getInstance("MD5");
//                md5.reset();
//                md5.update(sbSign.toString().getBytes("UTF-8"));
//
//                //  v2-{AK}-{ExpireTime}-{Signature}
//                token = String.format("%s-%s-%s-%s", TOKEN_VERSION, accessKey, expireTime,
//                        new String(Hex.encodeHex(md5.digest())));
//               // logger.info("signature token: {}", token);
//            } catch (UnsupportedEncodingException e) {
//                e.printStackTrace();
//                //logger.error("failed to decode url or query path");
//                throw new RuntimeException("Bad encoded url path or query string");
//            } catch (NoSuchAlgorithmException e) {
//                e.printStackTrace();
//            }
//
//            return token;
//        }

    public String generateToken(String urlPath, String method, String queryParam, String body, Integer expireTime) {
        if (StringUtils.isBlank(this.accessKey) || StringUtils.isBlank(this.secretKey)) {
            throw new IllegalArgumentException("Invalid AK or SK");
        }
        if (StringUtils.isBlank(urlPath)) {
            throw new IllegalArgumentException("Empty url path");
        }
        if (StringUtils.isBlank(method) || !this.allowedMethods.contains(method)) {
            throw new IllegalArgumentException("invalid request method");
        }

        try {
            // md5(|v2-{AK}-{ExpireTime}|{SK}|{UrlPath}|{Method}|{Querystring}|{Body}|)
            StringBuffer sbSign = new StringBuffer(String.format("|%s-%s-%d|%s|", TOKEN_VERSION, this.accessKey, expireTime, this.secretKey));
            sbSign.append(decodeUtf8(urlPath)).append("|").append(method).append("|");
            // b=111&c=1&ch=1
            if (StringUtils.isNotBlank(queryParam)) {
                List<String> queryParamList = new ArrayList<String>();
                String[] queryParamStr = queryParam.split("&");
                for (String kv : queryParamStr) {
                    String[] param = kv.split("=");
                    if (param.length > 1) {
                        queryParamList.add(String.format("%s=%s", decodeUtf8(param[0]), decodeUtf8(param[1])));
                    } else {
                        queryParamList.add(String.format("%s=", decodeUtf8(param[0])));
                    }
                }
                // 将转换后的所有字符串按照字典顺序排序;
                Collections.sort(queryParamList);

                // 将排序后的字符串按顺序用 & 符号链接起来, 即得到”合并值“
                boolean first = true;
                String nextParam;
                for (Iterator iterator = queryParamList.iterator(); iterator.hasNext(); sbSign.append(nextParam)) {
                    nextParam = (String) iterator.next();
                    if (first) {
                        first = false;
                    } else {
                        sbSign.append("&");
                    }
                }
            }
            sbSign.append("|");
            if (StringUtils.isNotBlank(body)) {
                sbSign.append(body);
            }
            sbSign.append("|");
            System.out.println("sb sign ; " + sbSign);
            MessageDigest digest = MessageDigest.getInstance("MD5");
            digest.reset();
            digest.update(sbSign.toString().getBytes(UTF8_ENCODING));
            return String.format("%s-%s-%s-%s", TOKEN_VERSION, this.accessKey, expireTime, new String(Hex.encodeHex(digest.digest())));
        } catch (Exception exception) {
            throw new IllegalStateException("Bad encoded url path or query string");
        }
    }

    public boolean verifyToken(String token, String urlPath, String method, String queryParam, String body) {
        if (StringUtils.isBlank(token)) {
            return false;
        }
        try {
            String[] tokenParts = token.split("-");
            if (tokenParts.length != TOKEN_PARTS_LENGTH) {
                return false;
            }
            if (!TOKEN_VERSION.equals(tokenParts[0])) {
                return false;
            }
            int expireTime = Integer.parseInt(tokenParts[2]);
            if ((long) expireTime < System.currentTimeMillis() / 1000L) {
                return false;
            }
            if (token.equals(this.generateToken(urlPath, method, queryParam, body, expireTime))) {
                return true;
            }
        } catch (Exception e) {
            return false;
        }

        return false;
    }

    private String decodeUtf8(String encodeUrl) {
        try {
            return URLDecoder.decode(encodeUrl, UTF8_ENCODING);
        } catch (UnsupportedEncodingException e) {
            return encodeUrl;
        }
    }

    public String getAccessKey() {
        return this.accessKey;
    }

    public void setAccessKey(String accessKey) {
        this.accessKey = accessKey;
    }

    public String getSecretKey() {
        return this.secretKey;
    }

    public void setSecretKey(String secretKey) {
        this.secretKey = secretKey;
    }

}
